﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using System.IO;
using System.Xml.Serialization;
using Kristall.MyGame.Scripts;
using System.ComponentModel;
using Kristall.MyGame.Enemies;
using Kristall.Engine.Scripts;

namespace Kristall.Engine
{
    //[XmlInclude(typeof(Animatable))]
    //[XmlInclude(typeof(FlyingEnemy)), XmlInclude(typeof(CrystalDrainerEnemy)), XmlInclude(typeof(TestEnemy))]

    //[XmlInclude(typeof(CheckPointScript))]
    //[XmlInclude(typeof(CheckPoint))]
    //[XmlInclude(typeof(DisappearScript))]
    //[XmlInclude(typeof(LavaScript))]
    //[XmlInclude(typeof(TriggerScript))]
    //[XmlInclude(typeof(PlatformScript))]
    public class AABB
    {
        #region Fields and Properties
        public event EventHandler<CollisionEventArgs> OnCollision;

        [Category("Collision")]
        public bool CheckPlayerCollision
        {
            get;
            set;
        }

        [Category("Collision")]
        public bool CheckWorldCollision
        {
            get;
            set;
        }

        [Category("Scripting"), Description("Id is used for scripting")]
        public string Id
        {
            get;
            set;
        }

        [Category("Scripting")]
        public virtual List<Script> Scripts
        {
            get;
            set;
        }

        [XmlIgnore, Category("Scripting")]
        public int NumberOfScripts
        {
            get
            {
                return Scripts.Count;
            }
        }

        public Vector2 Velocity = Vector2.Zero;

        private Vector2 _position = Vector2.Zero;

        [Category("Settings")]
        public Vector2 Position
        {
            get
            {
                return _position;
            }
            set
            {
                _position = value;
            }
        }

        const int MINSIZE = 10;

        int _height = 10;
        [Category("Settings")]
        public int Height
        {
            get
            {
                return _height;
            }
            set
            {
                _height = Math.Max(MINSIZE, value);
            }
        }

        int _width = 10;
        [Category("Settings")]
        public int Width
        {
            get
            {
                return _width;
            }
            set
            {
                _width = Math.Max(MINSIZE, value);
            }
        }

        [XmlIgnore, Category("Settings")]
        public virtual Vector2 Center
        {
            get
            {
                return new Vector2(Bounds.Center.X, Bounds.Center.Y);
            }
            set
            {
                this.Position = value - Size * 0.5f;
            }
        }

        [XmlIgnore, Browsable(false)]
        public Vector2 Size
        {
            get
            {
                return new Vector2(Width, Height);
            }
        }

        [XmlIgnore, Browsable(false)]
        public Vector2 HalfSize
        {
            get
            {
                return Size * 0.5f;
            }
        }

        [Browsable(false)]
        public Rectangle Bounds
        {
            get
            {
                return new Rectangle((int)Math.Round(_position.X), (int)Math.Round(_position.Y), Width, Height);
            }
        }

        private static Texture2D scriptIcon;

        #endregion

        public AABB()
        {
            initialize();
        }

        public AABB(int x, int y, int width, int height)
        {
            Width = width;
            Height = height;
            this.Position = new Vector2(x, y);
            initialize();
        }

        void initialize()
        {
            this.CheckPlayerCollision = true;
            this.CheckWorldCollision = false;
            this.Id = "Id_not_set";
            Scripts = new List<Script>();
        }

        public virtual void LoadContent(Level level)
        {
            Scripts.ForEach(x => x.Load(level, this));

            if (scriptIcon == null || scriptIcon.IsDisposed)
                scriptIcon = level.Content.Load<Texture2D>(@"Editor\Script");
        }

        public virtual void RemoveFromLevel()
        {
            throw new NotImplementedException();
        }

        public virtual void AddToLevel()
        {
            throw new NotImplementedException();
        }

        public void SetSize(int width, int height)
        {
            Width = width;
            Height = height;
        }

        public void SetSize(Vector2 vector)
        {
            this.SetSize((int)vector.X, (int)vector.Y);
        }

        public virtual void DrawDebug(SpriteBatch batch, Color tint, float layerDepth = 0)
        {
            if (Kristall.EnableDebug)
            {
                if (Scripts.Any())
                    batch.Draw(scriptIcon, Position, Color.White);

                batch.Draw(Kristall.Blank, Bounds, null, tint, 0, Vector2.Zero, SpriteEffects.None, layerDepth);
            }
        }

        public virtual void DrawDebug(SpriteBatch batch)
        {
            this.DrawDebug(batch, Color.Magenta * 0.25f);
        }

        public virtual void OnCollide(CollisionEventArgs collision)
        {
            if (OnCollision != null)
            {
                OnCollision(this, collision);
            }

            Scripts.ForEach(x => x.OnCollide(collision));
        }

        public virtual void Update(float dt)
        {
            Scripts.ForEach(x => x.Update(dt));
        }
    }

    public enum Direction
    {
        Top,
        Bottom,
        Left,
        Right,
    }

    public class CollisionEventArgs : EventArgs
    {
        public AABB Other;
        public Vector2 Translation;
        public Direction Direction;
        public bool Resolve = true;

        public CollisionEventArgs()
        {

        }

        public CollisionEventArgs(AABB other, Vector2 translation, Direction direction, bool resolve)
        {
            Other = other;
            Translation = translation;
            Direction = direction;
            Resolve = resolve;
        }

    }
    
    public static class AABBExtensions
    {
        /// <summary>
        /// Translation here returns the amount to move other, in order to resolve the collision
        /// And direction in collision results is relative to other
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="other"></param>
        /// <returns></returns>
        public static CollisionEventArgs Collides(this AABB obj, AABB other)
        {
            bool intersection = obj.Bounds.Intersects(other.Bounds);
            if (!intersection)
                return null;

            CollisionEventArgs result = new CollisionEventArgs();

            float vx = obj.Bounds.Center.X - other.Bounds.Center.X;
            float vy = other.Bounds.Center.Y - obj.Bounds.Center.Y;

            bool left = false;
            bool top = false;

            Vector2 trans = Vector2.Zero;

            if (vx < 0) //collision on left
            {
                trans.X = obj.Position.X + obj.Width - (other.Position.X);
                left = true;
            }
            else
            {
                trans.X = obj.Position.X - (other.Position.X + other.Width);
            }

            if (vy > 0) //collision on top
            {
                trans.Y = obj.Position.Y + obj.Height - (other.Position.Y);
                top = true;
            }
            else
            {
                trans.Y = -(other.Position.Y + other.Height - (obj.Position.Y));
            }

            if (Math.Abs(trans.Y) <= Math.Abs(trans.X)) //Collision resolve in Y-axis
            {
                trans.X = 0;
                result.Direction = top ? Direction.Top : Direction.Bottom;
            }
            else //if (Math.Abs(trans.X) < Math.Abs(trans.Y))
            {
                trans.Y = 0;
                result.Direction = left ? Direction.Left : Direction.Right;
            }

            result.Translation = trans;
            result.Other = other;

            return result;
        }
    }
}
